package com.cast.gwt.TicTacToe.client;
import com.cast.gwt.receiver.client.Channel;
import com.cast.gwt.receiver.client.ChannelHandler;
import com.cast.gwt.receiver.client.EventHandler;
import com.cast.gwt.receiver.client.EventType;
import com.cast.gwt.receiver.client.MessageEvent;
import com.cast.gwt.receiver.client.console;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.json.client.JSONArray;
import com.google.gwt.json.client.JSONBoolean;
import com.google.gwt.json.client.JSONNumber;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;
/**
* Tic Tac Toe Gameplay with Chromecast This file exposes cast.TicTacToe as an
* object containing a ChannelHandler and capable of receiving and sending
* messages to the sender application.
*/
public class TicTacToe
{
private static enum PLAYER
{
O, X;
};
public Board mBoard;
private Player mPlayer1;
private Player mPlayer2;
private String mCurrentPlayer;
public ChannelHandler mChannelHandler;
/**
* Creates a TicTacToe object with an optional board and attaches a
* cast.receiver.ChannelHandler, which receives messages from the channel
* between the sender and receiver.
*
* @param Board
* opt_board an optional game board.
*/
public TicTacToe(Board mBoard)
{
this.mBoard = mBoard;
this.mPlayer1 = null;
this.mPlayer2 = null;
this.mChannelHandler = ChannelHandler.create("TicTacToeDebug");
// Adds event listening functions to TicTacToe.prototype.
this.mChannelHandler.addEventListener(EventType.MESSAGE(),
new EventHandler()
{
@Override
public void onEvent(Event event)
{
onMessage((MessageEvent) event);
}
});
this.mChannelHandler.addEventListener(EventType.OPEN(), new EventHandler()
{
@Override
public void onEvent(Event event)
{
onChannelOpened(event);
}
});
this.mChannelHandler.addEventListener(EventType.CLOSED(),
new EventHandler()
{
@Override
public void onEvent(Event event)
{
onChannelClosed(event);
}
});
}
/**
* Channel opened event; checks number of open channels.
*
* @param event
* the channel open event.
*/
protected void onChannelOpened(Event event)
{
console.log("onChannelOpened. Total number of channels: "
+ (this.mChannelHandler.getChannels()).length());
}
/**
* Channel closed event; if all devices are disconnected, closes the
* application.
*
* @param event
* event the channel close event.
*/
private void onChannelClosed(Event event)
{
console.log("onChannelClosed. Total number of channels: "
+ (mChannelHandler.getChannels()).length());
if ((this.mChannelHandler.getChannels()).length() == 0)
{
Window.open("", "_parent", "");
closeBrowser();
}
}
public native void closeBrowser()
/*-{
$wnd.close();
}-*/;
/**
* Message received event; determines event message and command, and choose
* function to call based on them.
*
* @param MessageEvent
* event the event to be processed.
*/
public void onMessage(MessageEvent event)
{
JSONObject message = new JSONObject(event.message());
Channel channel = (Channel) event.target();
console.log("********onMessage******** " + (message));
console.log("mPlayer1: " + this.mPlayer1);
console.log("mPlayer2: " + this.mPlayer2);
if (message.get("command").isString().stringValue()
.equalsIgnoreCase("join"))
{
this.onJoin(channel, message);
}
else if (message.get("command").isString().stringValue()
.equalsIgnoreCase("leave"))
{
this.onLeave(channel);
}
else if (message.get("command").isString().stringValue()
.equalsIgnoreCase("move"))
{
this.onMove(channel, message);
}
else if (message.get("command").isString().stringValue()
.equalsIgnoreCase("board_layout_request"))
{
this.onBoardLayoutRequest(channel);
}
else
{
console.log("Invalid message command: "
+ message.get("command").isString().stringValue());
}
}
/**
* Request event for the board layout: sends the current layout of pieces on
* the board through the channel.
*
* @param Channel
* channel the channel the event came from.
*/
private void onBoardLayoutRequest(Channel channel)
{
console.log("****onBoardLayoutRequest");
JSONArray array = new JSONArray();
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
array.set(i * 3 + j, new JSONNumber(this.mBoard.mBoard[i][j]));
}
}
JSONObject data = new JSONObject();
data.put("event", new JSONString("board_layout_response"));
data.put("board", array);
channel.send(data);
}
/**
* Move event: checks whether a valid move was made and updates the board as
* necessary.
*
* @param Channel
* channel the source of the move, which determines the player.
* @param JSONObject
* message contains the row and column of the move.
*/
private void onMove(Channel channel, JSONObject message)
{
console.log("****onMove: " + message);
boolean isMoveValid;
if ((this.mPlayer1 == null) || (this.mPlayer2 == null))
{
console.log("Looks like one of the players is not there");
console.log("mPlayer1: " + this.mPlayer1);
console.log("mPlayer2: " + this.mPlayer2);
return;
}
if (this.mPlayer1.getChannel() == channel)
{
if (this.mPlayer1.getPlayer() == this.mCurrentPlayer)
{
if (this.mPlayer1.getPlayer() == PLAYER.X.name())
{
isMoveValid = this.mBoard.drawCross(message.get("row").isNumber()
.doubleValue(), message.get("column").isNumber().doubleValue());
}
else
{
isMoveValid = this.mBoard.drawNaught(message.get("row").isNumber()
.doubleValue(), message.get("column").isNumber().doubleValue());
}
}
else
{
console.log("Ignoring the move. It\"s not your turn.");
this.sendError(channel, "It\"s not your turn.");
return;
}
}
else if (this.mPlayer2.getChannel() == channel)
{
if (this.mPlayer2.getPlayer() == this.mCurrentPlayer)
{
if (this.mPlayer2.getPlayer() == PLAYER.X.name())
{
isMoveValid = this.mBoard.drawCross(message.get("row").isNumber()
.doubleValue(), message.get("column").isNumber().doubleValue());
}
else
{
isMoveValid = this.mBoard.drawNaught(message.get("row").isNumber()
.doubleValue(), message.get("column").isNumber().doubleValue());
}
}
else
{
console.log("Ignoring the move. It\"s not your turn.");
this.sendError(channel, "It\"s not your turn.");
return;
}
}
else
{
console.log("Ignorning message. Someone other than the current"
+ "players sent a move.");
this.sendError(channel, "You are not playing the game");
return;
}
if (isMoveValid == false)
{
this.sendError(channel, "Your last move was invalid");
return;
}
boolean isGameOver = this.mBoard.isGameOver();
JSONObject data = new JSONObject();
data.put("event", new JSONString("moved"));
data.put("player", new JSONString(this.mCurrentPlayer));
data.put("row", message.get("row").isNumber());
data.put("column", message.get("column").isNumber());
data.put("game_over", JSONBoolean.getInstance(isGameOver));
this.broadcast(data);
console.log("isGameOver: " + isGameOver);
console.log("winningLoc: " + this.mBoard.getWinningLocation());
// When the game should end
if (isGameOver == true)
{
this.broadcastEndGame(this.mBoard.getGameResult(),
this.mBoard.getWinningLocation());
}
// Switch current player
this.mCurrentPlayer = (this.mCurrentPlayer
.equalsIgnoreCase(PLAYER.X.name()) ? TicTacToe.PLAYER.O.name()
: TicTacToe.PLAYER.X.name());
}
private void broadcastEndGame(String gameResult, int winningLocation)
{
console.log("****endGame");
this.mPlayer1 = null;
this.mPlayer2 = null;
JSONObject data = new JSONObject();
data.put("event", new JSONString("endgame"));
data.put("end_state", new JSONString(gameResult));
data.put("winning_location", new JSONNumber(winningLocation));
this.broadcast(data);
}
private void sendError(Channel channel, String errorMessage)
{
JSONObject data = new JSONObject();
data.put("event", new JSONString("board_layout_response"));
data.put("message", new JSONString(errorMessage));
channel.send(data);
}
/**
* Player leave event: determines which player left and unregisters that
* player, and ends the game if all players are absent.
*
* @param Channel
* channel the channel of the leaving player.
*/
private void onLeave(Channel channel)
{
console.log("****OnLeave");
if (this.mPlayer1 != null && this.mPlayer1.getChannel() == channel)
{
this.mPlayer1 = null;
}
else if (this.mPlayer2 != null && this.mPlayer2.getChannel() == channel)
{
this.mPlayer2 = null;
}
else
{
console.log("Neither player left the game");
return;
}
console.log("mBoard.GameResult: " + this.mBoard.getGameResult());
if (this.mBoard.getGameResult() == null)
{
this.mBoard.setGameAbandoned();
this.broadcastEndGame(this.mBoard.getGameResult(), 0);
}
}
/**
* Player joined event: registers a new player who joined the game, or
* prevents player from joining if invalid.
*
* @param Channel
* channel the channel the message came from.
* @param JSONObject
* message the name of the player who just joined.
*/
private void onJoin(Channel channel, JSONObject message)
{
console.log("****onJoin: " + message);
if ((this.mPlayer1 != null) && (this.mPlayer1.getChannel() == channel))
{
this.sendError(channel, "You are already " + this.mPlayer1.getPlayer()
+ " You aren\"t allowed to play against yourself.");
return;
}
if ((this.mPlayer2 != null) && (this.mPlayer2.getChannel() == channel))
{
this.sendError(channel, "You are already " + this.mPlayer2.getPlayer()
+ " You aren\"t allowed to play against yourself.");
return;
}
if (this.mPlayer1 == null)
{
this.mPlayer1 = JavaScriptObject.createObject().cast();
this.mPlayer1.setName(message.get("name").isString().stringValue());
this.mPlayer1.setChannel(channel);
}
else if (this.mPlayer2 == null)
{
this.mPlayer2 = JavaScriptObject.createObject().cast();
this.mPlayer2.setName(message.get("name").isString().stringValue());
this.mPlayer2.setChannel(channel);
}
else
{
console.log("Unable to join a full game.");
this.sendError(channel, "Game is full.");
return;
}
console.log("mPlayer1: " + this.mPlayer1);
console.log("mPlayer2: " + this.mPlayer2);
if (this.mPlayer1 != null && this.mPlayer2 != null)
{
this.mBoard.reset();
this.startGame_();
}
}
private void startGame_()
{
console.log("****startGame");
int firstPlayer = (int) Math.floor((Math.random() * 10) % 2);
this.mPlayer1.setPlayer((firstPlayer == 0) ? TicTacToe.PLAYER.X.name()
: TicTacToe.PLAYER.O.name());
this.mPlayer2.setPlayer((firstPlayer == 0) ? TicTacToe.PLAYER.O.name()
: TicTacToe.PLAYER.X.name());
this.mCurrentPlayer = PLAYER.X.name();
JSONObject data1 = new JSONObject();
data1.put("event", new JSONString("joined"));
data1.put("player", new JSONString(this.mPlayer1.getPlayer()));
data1.put("opponent", new JSONString(this.mPlayer2.getName()));
this.mPlayer1.getChannel().send(data1);
JSONObject data2 = new JSONObject();
data2.put("event", new JSONString("joined"));
data2.put("player", new JSONString(this.mPlayer2.getPlayer()));
data2.put("opponent", new JSONString(this.mPlayer1.getName()));
this.mPlayer2.getChannel().send(data2);
}
/**
* Broadcasts a message to all of this object's known channels.
*
* @param JSONObject
* message the message to broadcast.
*/
private void broadcast(JSONObject message)
{
JsArray<Channel> arr = this.mChannelHandler.getChannels();
for (int i = 0; i < arr.length(); i++)
{
arr.get(i).send(message);
}
}
}